﻿// Jinx AJAX Framework
// Copyright (C) 2008 Blake Hegerle
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace Jinx.Models
{
    public class ModelBase
    {
        #region Delegates

        public delegate void MemberChanged(ModelBase Model, string MemberName, object OldValue, object NewValue);

        #endregion

        private readonly Dictionary<string, object> InMemberChangedEvent;
        private readonly Dictionary<string, ModelMember> Members;
        private readonly Dictionary<string, object> MemberValues;

        public ModelBase(string ModelClass, IEnumerable<ModelMember> Members)
        {
            this.Members = new Dictionary<string, ModelMember>();
            MemberValues = new Dictionary<string, object>();
            InMemberChangedEvent = new Dictionary<string, object>();

            this.ModelClass = ModelClass;

            foreach (var NewMember in Members)
            {
                if (this.Members.ContainsKey(NewMember.Name))
                    throw new DuplicateMember(ModelClass, NewMember.Name);

                this.Members[NewMember.Name] = NewMember;
            }
        }

        internal string ModelClass { get; private set; }

        internal IDictionary<string, object> AllMembersValues
        {
            get
            {
                var MemberDict = new Dictionary<string, object>();

                foreach (var Member in Members)
                {
                    if (MemberValues.ContainsKey(Member.Key))
                        MemberDict[Member.Key] = MemberValues[Member.Key];
                    else
                        MemberDict[Member.Key] = null;
                }

                return MemberDict;
            }
        }

        public event MemberChanged MemberChangedEvent;

        public IDictionary<string, object> GetAllMembersValues()
        {
            return AllMembersValues;
        }

        public void Update<T>(string MemberName, T Value)
        {
            object OldValue;

            OldValue = MemberValues.ContainsKey(MemberName)
                           ? MemberValues[MemberName]
                           : null;

            if (Value != null)
                if (Value is T == false)
                    throw new TypeMismatch(ModelClass, typeof(T), Value.GetType());

            MemberValues[MemberName] = Value;

            OnMemberChangedEvent(this, MemberName, OldValue, Value);
        }

        public T Get<T>(string MemberName)
        {
            object Value;

            if (MemberValues.TryGetValue(MemberName, out Value))
            {
                if (Value == null)
                {
                    return default(T);
                }

                if (Value is T == false)
                    throw new TypeMismatch(ModelClass, typeof(T), Value.GetType());

                return (T)Value;
            }

            return default(T);
        }

        public Type GetMemberType(string MemberName)
        {
            ModelMember ModelMember;

            if (Members.TryGetValue(MemberName, out ModelMember))
                return ModelMember.Type;

            throw new MemberNotFound(ModelClass, MemberName);
        }

        protected void AddMember(ModelMember Member)
        {
            if (Members.ContainsKey(Member.Name))
                throw new DuplicateMember(ModelClass, Member.Name);

            Members[Member.Name] = Member;
        }

        protected void RemoveMember(ModelMember Member)
        {
            if (Members.ContainsKey(Member.Name) == false)
                throw new MemberNotFound(ModelClass, Member.Name);

            Members.Remove(Member.Name);
        }

        private void OnMemberChangedEvent(ModelBase Model, string MemberName, object OldValue, object NewValue)
        {
            if (MemberChangedEvent != null)
            {
                if (MemberIsChanging(MemberName) == false)
                {
                    BeginChangingMember(MemberName);

                    try
                    {
                        MemberChangedEvent(Model, MemberName, OldValue, NewValue);
                    }
                    finally
                    {
                        EndChangingMember(MemberName);
                    }
                }
            }
        }

        private bool MemberIsChanging(string MemberName)
        {
            return InMemberChangedEvent.ContainsKey(MemberName);
        }

        private void BeginChangingMember(string MemberName)
        {
            InMemberChangedEvent[MemberName] = null;
        }

        private void EndChangingMember(string MemberName)
        {
            InMemberChangedEvent.Remove(MemberName);
        }

        //public override bool Equals(object obj)
        //{
        //    var That = obj as ModelBase;

        //    if (That == null)
        //        return false;

        //    if (this == That)
        //        return true;

        //    if (ModelClass != That.ModelClass)
        //        return false;

        //    if (MemberValues.Count != That.MemberValues.Count)
        //        return false;

        //    foreach (var Member in MemberValues)
        //    {
        //        object Val;

        //        if (!That.MemberValues.TryGetValue(Member.Key, out Val))
        //            return false;

        //        if (!Member.Value.Equals(Val))
        //            return false;
        //    }

        //    return true;
        //}
    }
}